package com.sagframe.sagacity.sqltoy.plus.multi;

import com.sagframe.sagacity.sqltoy.plus.MultiFiledMappingStrategy;
import com.sagframe.sagacity.sqltoy.plus.conditions.ISqlSegment;
import com.sagframe.sagacity.sqltoy.plus.conditions.eumn.CompareEnum;
import com.sagframe.sagacity.sqltoy.plus.conditions.eumn.SqlKeyword;
import com.sagframe.sagacity.sqltoy.plus.FiledValueFilterStrategy;
import com.sagframe.sagacity.sqltoy.plus.conditions.segments.SqlSegmentMeta;
import com.sagframe.sagacity.sqltoy.plus.conditions.toolkit.FiledNature;
import com.sagframe.sagacity.sqltoy.plus.conditions.toolkit.StringPool;
import com.sagframe.sagacity.sqltoy.plus.multi.interfaces.StringCondition;
import org.sagacity.sqltoy.utils.StringUtil;

import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;

public abstract class AbstractStringMultiWrapper<Children extends AbstractStringMultiWrapper<Children>> extends AbstractMultiWrapper<Children> implements StringCondition<Children> {

    public AbstractStringMultiWrapper(Class<?> toClass) {
        super(toClass);
        context.addSqlAssembler(mappingStrategy -> {
            ISqlSegment sqlSegment = typedThis.getISqlSegment(mappingStrategy);
            if (sqlSegment != null) {
                String conditionSql = sqlSegment.getSqlSegment();
                if (conditionSql != null && !conditionSql.isEmpty()) {
                    context.addConditionSqlSegment(new ISqlSegment() {
                        @Override
                        public String getSqlSegment() {
                            return conditionSql;
                        }
                        public Map<String, Object> getSqlSegmentParamMap() {
                            return sqlSegment.getSqlSegmentParamMap();
                        }
                    });
                }
            }
        });
    }

    public AbstractStringMultiWrapper(MultiContext context) {
        super(context);
    }

    /**
     * 子类返回一个自己的新对象
     * @return
     *              -返回自身
     */
    protected abstract StringCondition<Children> instance();

    /**
     * 普通查询条件
     *
     * @param condition  是否执行
     * @param column     属性
     * @param compareEnum SQL 关键词
     * @param val        条件值
     */
    private Children addNeedValCondition(MultiFiledMappingStrategy mappingStrategy, boolean condition, String column, CompareEnum compareEnum, Object val) {
        return maybeDo(condition, () -> addSqlSegment(mappingStrategy, column, compareEnum, val));
    }

    /**
     * 设置查询条件封装的单元对象
     * @param column
     * @param compareEnum
     * @param val
     */
    private <T> void addSqlSegment(MultiFiledMappingStrategy mappingStrategy, String column, CompareEnum compareEnum, Object val) {
        boolean isAllow = FiledValueFilterStrategy.FiledValueFilterStrategyHolder.getInstance().validate(val);
        if (isAllow) {
            FiledNature nature = context.getFiledNature(column);
            final String paramName = context.getParamName(nature.getFiledName());
            String columnName = mappingStrategy.getColumnName(context, nature.getClazz(), nature.getFiledName());
            SqlSegmentMeta sqlSegmentMeta = new SqlSegmentMeta();
            sqlSegmentMeta.setCompareEnum(compareEnum);
            sqlSegmentMeta.setEntityFiledName(nature.getFiledName());
            sqlSegmentMeta.setParamName(paramName);
            sqlSegmentMeta.setColumnName(columnName);
            sqlSegmentMeta.putPair(paramName, val);
            appendSqlSegments(sqlSegmentMeta);
        }
    }

    /**
     * 获取属性名称
     * @param column
     *             -抽象的属性定义
     * @return
     *             -返回自身
     */
    private <T> String columnToString(MultiFiledMappingStrategy mappingStrategy, String column) {
        FiledNature nature = context.getFiledNature(column);
        return mappingStrategy.getColumnName(context, nature.getClazz(), nature.getFiledName());
    }

    private <T> void addBatchInCondition(MultiFiledMappingStrategy mappingStrategy, boolean condition, Collection<String> columns, Collection<Object[]> values) {
        List<FiledNature> natures = columns.stream().map(context::getFiledNature).collect(Collectors.toList());
        List<String> columnNames = new ArrayList<>();
        List<String> paramNames = new ArrayList<>();
        Map<String, Object> paramMap = new HashMap<>();
        for (int i = 0; i < natures.size(); i++) {
            FiledNature nature = natures.get(i);
            columnNames.add(mappingStrategy.getColumnName(context, nature.getClazz(), nature.getFiledName()));
            String paramName = context.getParamName(nature.getFiledName());
            paramNames.add(paramName);
            int finalI = i;
            paramMap.put(paramName, values.stream().map(e -> e[finalI]).collect(Collectors.toList()));
        }
        maybeDo(condition, () -> appendSqlSegments(new ISqlSegment() {
            @Override
            public String getSqlSegment() {
                return CompareEnum.IN_BATCH.getBatchMetaSql(paramNames, columnNames);
            }
            @Override
            public Map<String, Object> getSqlSegmentParamMap() {
                return paramMap;
            }
        }));
    }

    @Override
    public StringCondition<Children> eq(boolean condition, String column, Object val) {
        if (!validateFiledValue(val)) {
            return typedThis;
        }
        return addAssembler((strategy) -> addNeedValCondition(strategy, condition, column, CompareEnum.EQ, val));
    }

    @Override
    public StringCondition<Children> ne(boolean condition, String column, Object val) {
        if (!validateFiledValue(val)) {
            return typedThis;
        }
        return addAssembler((strategy) -> addNeedValCondition(strategy, condition, column, CompareEnum.NE, val));
    }

    @Override
    public StringCondition<Children> gt(boolean condition, String column, Object val) {
        if (!validateFiledValue(val)) {
            return typedThis;
        }
        return addAssembler((strategy) -> addNeedValCondition(strategy, condition, column, CompareEnum.GT, val));
    }

    @Override
    public StringCondition<Children> ge(boolean condition, String column, Object val) {
        if (!validateFiledValue(val)) {
            return typedThis;
        }
        return addAssembler((strategy) -> addNeedValCondition(strategy, condition, column, CompareEnum.GE, val));
    }

    @Override
    public StringCondition<Children> lt(boolean condition, String column, Object val) {
        if (!validateFiledValue(val)) {
            return typedThis;
        }
        return addAssembler((strategy) -> addNeedValCondition(strategy, condition, column, CompareEnum.LT, val));
    }

    @Override
    public StringCondition<Children> le(boolean condition, String column, Object val) {
        if (!validateFiledValue(val)) {
            return typedThis;
        }
        return addAssembler((strategy) -> addNeedValCondition(strategy, condition, column, CompareEnum.LE, val));
    }

    @Override
    public StringCondition<Children> between(boolean condition, String column, Object val1, Object val2) {
        if (!validateFiledValue(val1) || !validateFiledValue(val2)) {
            return typedThis;
        }
        return addAssembler((strategy) -> {
            maybeDo(condition, () -> {
                FiledNature nature = context.getFiledNature(column);
                String val1Name = context.getParamName(nature.getFiledName());
                String val2Name = context.getParamName(nature.getFiledName());
                appendSqlSegments(new ISqlSegment() {
                    @Override
                    public String getSqlSegment() {
                        return CompareEnum.BETWEEN.getMetaSql(strategy.getColumnName(context, nature.getClazz(), nature.getFiledName()), val1Name, val2Name);
                    }
                    @Override
                    public Map<String, Object> getSqlSegmentParamMap() {
                        HashMap<String, Object> map = new HashMap<>();
                        map.put(val1Name, val1);
                        map.put(val2Name, val2);
                        return map;
                    }
                });
            });
        });
    }

    @Override
    public StringCondition<Children> notBetween(boolean condition, String column, Object val1, Object val2) {
        if (!validateFiledValue(val1) || !validateFiledValue(val2)) {
            return typedThis;
        }
        return addAssembler((strategy) -> {
            maybeDo(condition, () -> {
                FiledNature nature = context.getFiledNature(column);
                String val1Name = context.getParamName(nature.getFiledName());
                String val2Name = context.getParamName(nature.getFiledName());
                appendSqlSegments(new ISqlSegment() {
                    @Override
                    public String getSqlSegment() {
                        return CompareEnum.NOT_BETWEEN.getMetaSql(strategy.getColumnName(context, nature.getClazz(), nature.getFiledName()), val1Name, val2Name);
                    }
                    @Override
                    public Map<String, Object> getSqlSegmentParamMap() {
                        HashMap<String, Object> map = new HashMap<>();
                        map.put(val1Name, val1);
                        map.put(val2Name, val2);
                        return map;
                    }
                });
            });
        });
    }

    @Override
    public StringCondition<Children> like(boolean condition, String column, Object val) {
        if (!validateFiledValue(val)) {
            return typedThis;
        }
        return addAssembler((strategy) -> addNeedValCondition(strategy, condition, column, CompareEnum.LIKE, val));
    }

    @Override
    public StringCondition<Children> notLike(boolean condition, String column, Object val) {
        if (!validateFiledValue(val)) {
            return typedThis;
        }
        return addAssembler((strategy) -> addNeedValCondition(strategy, condition, column, CompareEnum.NOT_LIKE, val));
    }

    @Override
    public StringCondition<Children> likeLeft(boolean condition, String column, Object val) {
        if (!validateFiledValue(val)) {
            return typedThis;
        }
        return addAssembler((strategy) -> addNeedValCondition(strategy, condition, column, CompareEnum.LIKE_LEFT, val));
    }

    @Override
    public StringCondition<Children> likeRight(boolean condition, String column, Object val) {
        if (!validateFiledValue(val)) {
            return typedThis;
        }
        return addAssembler((strategy) -> addNeedValCondition(strategy, condition, column, CompareEnum.LIKE_RIGHT, val));
    }

    @Override
    public StringCondition<Children> isNull(boolean condition, String column) {
        return addAssembler((strategy) -> maybeDo(condition, () -> {
            appendSqlSegments(() -> CompareEnum.IS_NULL.getMetaSql(columnToString(strategy, column)));
        }));
    }

    @Override
    public StringCondition<Children> isNotNull(boolean condition, String column) {
        return addAssembler((strategy) -> maybeDo(condition, () -> {
            appendSqlSegments(() -> CompareEnum.IS_NOT_NULL.getMetaSql(columnToString(strategy, column)));
        }));
    }

    @Override
    public StringCondition<Children> in(boolean condition, String column, Collection<?> coll) {
        if (!validateFiledValue(coll)) {
            return typedThis;
        }
        return addAssembler((strategy) -> addNeedValCondition(strategy, condition, column, CompareEnum.IN, coll));
    }

    @Override
    public StringCondition<Children> in(boolean condition, String column, Object... values) {
        if (!validateFiledValue(values)) {
            return typedThis;
        }
        return addAssembler((strategy) -> addNeedValCondition(strategy, condition, column, CompareEnum.IN, values));
    }

    @Override
    public StringCondition<Children> inb(boolean condition, Collection<String> columns, Collection<Object[]> values) {
        if (columns == null || columns.size() == 0 || values == null || values.size() == 0) {
            return typedThis;
        }
        return addAssembler((strategy) -> addBatchInCondition(strategy, condition, columns, values));
    }

    @Override
    public StringCondition<Children> notIn(boolean condition, String column, Collection<?> coll) {
        if (!validateFiledValue(coll)) {
            return typedThis;
        }
        return addAssembler((strategy) -> addNeedValCondition(strategy, condition, column, CompareEnum.NOT_IN, coll));
    }

    @Override
    public StringCondition<Children> notIn(boolean condition, String column, Object... values) {
        if (!validateFiledValue(values)) {
            return typedThis;
        }
        return addAssembler((strategy) -> addNeedValCondition(strategy, condition, column, CompareEnum.NOT_IN, values));
    }

    @Override
    public StringCondition<Children> groupBy(boolean condition, String column) {
        return addAssembler((strategy) -> {
            maybeDo(condition, () -> appendSqlSegments(SqlKeyword.GROUP_BY, () -> columnToString(strategy, column)));
        });
    }

    @Override
    public StringCondition<Children> groupBy(boolean condition, List<String> columns) {
        return addAssembler((strategy) -> {
            List<FiledNature> natures = columns.stream().map(context::getFiledNature).collect(Collectors.toList());
            maybeDo(condition, () -> appendSqlSegments(SqlKeyword.GROUP_BY, () -> strategy.getSplitColumnName(context, natures, StringPool.COMMA)));
        });
    }

    @Override
    public StringCondition<Children> groupBy(boolean condition, String column, String... columns) {
        List<String> list = new ArrayList<>();
        if (column != null) {
            list.add(column);
        }
        if (columns != null && columns.length > 0) {
            list.addAll(Arrays.asList(columns));
        }
        return groupBy(condition, list);
    }

    @Override
    public StringCondition<Children> orderBy(boolean condition, boolean isAsc, String column) {
        return addAssembler((strategy) -> {
            maybeDo(condition, () -> appendSqlSegments(SqlKeyword.ORDER_BY, () -> columnToString(strategy, column),
                    isAsc ? SqlKeyword.ASC : SqlKeyword.DESC));
        });
    }

    @Override
    public StringCondition<Children> orderBy(boolean condition, boolean isAsc, List<String> columns) {
        return addAssembler((strategy) -> {
            maybeDo(condition, () -> {
                List<FiledNature> natures = columns.stream().map(context::getFiledNature).collect(Collectors.toList());
                natures.forEach(c -> appendSqlSegments(SqlKeyword.ORDER_BY, () -> strategy.getColumnName(context, c.getClazz(), c.getFiledName()), isAsc ? SqlKeyword.ASC : SqlKeyword.DESC));
            });
        });
    }

    @Override
    public StringCondition<Children> orderBy(boolean condition, boolean isAsc, String column, String... columns) {
        return addAssembler((strategy) -> {
            final SqlKeyword mode = isAsc ? SqlKeyword.ASC : SqlKeyword.DESC;
            appendSqlSegments(SqlKeyword.ORDER_BY, () -> columnToString(strategy, column), mode);
            if (columns != null && columns.length > 0) {
                Arrays.stream(columns).forEach(c -> appendSqlSegments(SqlKeyword.ORDER_BY,
                        () -> columnToString(strategy, c), mode));
            }
        });
    }

    @Override
    public StringCondition<Children> having(boolean condition, String sqlHaving, Map<String, Object> paramMap) {
        if (!validateFiledValue(paramMap)) {
            return typedThis;
        }
        return addAssembler((strategy) -> {
            maybeDo(condition, () -> appendSqlSegments(SqlKeyword.HAVING, new ISqlSegment() {
                @Override
                public String getSqlSegment() {
                    return sqlHaving;
                }
                @Override
                public Map<String, Object> getSqlSegmentParamMap() {
                    return paramMap;
                }
            }));
        });
    }

    @Override
    public StringCondition<Children> accept(boolean condition, Consumer<StringCondition<Children>> consumer) {
        return addAssembler((strategy) -> {
            maybeDo(condition, () -> consumer.accept(typedThis));
        });
    }

    @Override
    public Children or(boolean condition) {
        return addAssembler((strategy) -> {
            maybeDo(condition, () -> appendSqlSegments(SqlKeyword.OR));
        });
    }

    @Override
    public Children and(boolean condition) {
        return addAssembler((strategy) -> {
            maybeDo(condition, () -> appendSqlSegments(SqlKeyword.AND));
        });
    }

    @Override
    public StringCondition<Children> last(boolean condition, String lastSql) {
        return addAssembler((strategy) -> {
            maybeDo(condition, () -> setLastSql(lastSql));
        });
    }

    @Override
    public StringCondition<Children> and(boolean condition, Function<StringCondition<Children>, StringCondition<Children>> function) {
        return and(condition).addAssembler((strategy) -> {
            addNestedCondition(strategy,condition, function);
        });
    }

    @Override
    public StringCondition<Children> or(boolean condition, Function<StringCondition<Children>, StringCondition<Children>> function) {
        return or(condition).addAssembler((strategy) -> {
            addNestedCondition(strategy,condition, function);
        });
    }

    @Override
    public StringCondition<Children> nested(boolean condition, Function<StringCondition<Children>, StringCondition<Children>> function) {
        return addAssembler((strategy) -> {
            addNestedCondition(strategy,condition, function);
        });
    }

    @Override
    public StringCondition<Children> not(boolean condition, Function<StringCondition<Children>, StringCondition<Children>> function) {
        return not(condition).addAssembler((strategy) -> {
            addNestedCondition(strategy,condition, function);
        });
    }

    @Override
    public ISqlSegment getISqlSegment(MultiFiledMappingStrategy mappingStrategy) {
        return super.getISqlSegment(mappingStrategy);
    }

    /**
     * 多重嵌套查询条件
     *
     * @param condition 查询条件值
     */
    private Children addNestedCondition(MultiFiledMappingStrategy mappingStrategy, boolean condition, Function<StringCondition<Children>, StringCondition<Children>> function) {
        return maybeDo(condition, () -> {
            final StringCondition<Children> instance = instance();
            StringCondition<Children> nested = function.apply(instance);
            ISqlSegment sqlSegment = nested.getISqlSegment(mappingStrategy);
            if (sqlSegment != null) {
                String sql = sqlSegment.getSqlSegment();
                if (StringUtil.isNotBlank(sql)) {
                    appendSqlSegments(new ISqlSegment() {
                        @Override
                        public String getSqlSegment() {
                            return StringPool.LEFT_BRACKET + sql + StringPool.RIGHT_BRACKET;
                        }

                        @Override
                        public Map<String, Object> getSqlSegmentParamMap() {
                            return sqlSegment.getSqlSegmentParamMap();
                        }
                    });
                }
            }
        });
    }
}
